/*
	Copyright (c) 2010 <copyright holders>
	This source code is licensed under The MIT License,
	see the file LICENSE for details.
	*/

#include "MessageFile.hpp"

#include "gen/auxmsg.pb.h"

namespace spelprog2 {
namespace file {

MessageInputFile::MessageInputFile(const std::string & filename, const char magic[]) :
    filename_(filename),
    inputStream_(filename.c_str(), std::ios_base::binary | std::ios_base::in)
{
    if (!inputStream_.is_open()) {
        throw MessageFileException("Cannot open " + filename + " for input", WHERE);
    }

    assert(!inputStream_.fail());
    if (magic) {
        const size_t MAGIC_LEN = 4;
        assert(strlen(magic) >= MAGIC_LEN);
        char firstFourBytes[MAGIC_LEN];
        inputStream_.read(firstFourBytes, MAGIC_LEN);

        if (memcmp(magic, firstFourBytes, MAGIC_LEN) != 0) {
            gamelog << firstFourBytes;
            throw MessageFileException(filename + " is not of expected message file type", WHERE);
        }
    }

    checkForStreamFailure();
}

void MessageInputFile::loadMessage(MessagePtr & msg)
{
    char buffer[MAX_MESSAGE_BODY_LEN];
    
    google::protobuf::uint32 msgSize;
    inputStream_.read(buffer, sizeof(msgSize));
    google::protobuf::io::CodedInputStream::ReadLittleEndian32FromArray(reinterpret_cast<google::protobuf::uint8*>(buffer), &msgSize);
    
    //special handling for EOF here, for some reason EOF isn't detected by finished()
    if (inputStream_.eof()) {
        msg.reset(new auxmsg::ErrorMessage());
        return;
    }

    checkForStreamFailure();
    
    if (msgSize > MAX_MESSAGE_BODY_LEN) {
        throw MessageFileException("Too large message stored in " + filename_, WHERE);
    }

    inputStream_.read(buffer, msgSize);
    google::protobuf::io::CodedInputStream codedStream(reinterpret_cast<google::protobuf::uint8*>(buffer), sizeof(buffer));
    google::protobuf::uint32 msgType;
    codedStream.ReadVarint32(&msgType);

    checkForStreamFailure();

    MessagePtr m = MessageTypeRegistry::getInstance().createMsg(msgType);
    if (m->GetDescriptor() == auxmsg::ErrorMessage::descriptor()) {
        throw MessageFileException("Unrecognizable message type in " + filename_, WHERE);
    }

    if (!m->ParseFromCodedStream(&codedStream)) {
        /*gamelog << "Bad message parsed:";
        gamelog << m->DebugString();
        throw MessageFileException("Bad message data in " + filename_ + " in message of type " + m->GetTypeName(), WHERE);*/
    }

    checkForStreamFailure();

    msg = m;
}

void MessageInputFile::checkForStreamFailure()
{
    if (inputStream_.fail()) {
        throw MessageFileException("Problems when reading " + filename_, WHERE);
    }
}

MessageOutputFile::MessageOutputFile(const std::string & filename, bool truncate, const char magic[]) :
    filename_(filename),
    outputStream_(filename.c_str(), std::ios_base::binary | (truncate? std::ios_base::trunc : std::ios_base::app)),
    adapterStream_(&outputStream_),
    codedStream_(&adapterStream_)
{
    if (!outputStream_.is_open()) {
        throw MessageFileException("Cannot open " + filename + " for output", WHERE);
    }

    if (magic) {
        const size_t MAGIC_LEN = 4;
        assert(strlen(magic) >= MAGIC_LEN);
        outputStream_.write(magic, MAGIC_LEN);
    }

    checkForStreamFailure();
}

void MessageOutputFile::storeMessage(const Message & msg)
{
    assert(msg.IsInitialized());
    assert(MessageTypeRegistry::getInstance().getMsgType(msg) != MessageTypeRegistry::UNRECOGNIZED_TYPE);

    const MessageTypeRegistry::TypeId type = MessageTypeRegistry::getInstance().getMsgType(msg);
    size_t size = msg.ByteSize() + codedStream_.VarintSize32(type);
    assert(size <= MAX_MESSAGE_BODY_LEN);

    codedStream_.WriteLittleEndian32(size);
    codedStream_.WriteVarint32(type);
    msg.SerializeToCodedStream(&codedStream_);

    checkForStreamFailure();
}

void MessageOutputFile::checkForStreamFailure()
{
    if (outputStream_.fail()) {
        throw MessageFileException("Problems when writing" + filename_, WHERE);
    }
}

}; //file
}; //spelprog2